import CheckCircleOutlined from "@ant-design/icons/CheckCircleOutlined"
import CheckOutlined from "@ant-design/icons/CheckOutlined"
import DeleteFilled from "@ant-design/icons/DeleteFilled"
import DeleteRowOutlined from "@ant-design/icons/DeleteRowOutlined"
import EditOutlined from "@ant-design/icons/EditOutlined"
import ExpandAltOutlined from "@ant-design/icons/ExpandAltOutlined"
import LoadingOutlined from "@ant-design/icons/LoadingOutlined"
import PlusOutlined from "@ant-design/icons/PlusOutlined"
import RollbackOutlined from "@ant-design/icons/RollbackOutlined"
import { PageList } from "@mallfoundry/shared/data"
import { Blob, BlobResource, BlobType, StorageService } from "@mallfoundry/storage"

import {
  Button,
  Checkbox,
  Divider,
  Empty,
  Form,
  Input,
  message,
  Modal,
  Pagination,
  Popconfirm,
  Popover,
  Spin,
  Tree,
  Typography,
} from "antd"
import classNames from "classnames"
import * as copy from "copy-to-clipboard"
import * as _ from "lodash"
import * as React from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import Dropzone from "react-dropzone"
import { useParams } from "react-router-dom"
import { DragUploadImageSvg } from "./blob-icons"
import classes from "./image-blobs.module.scss"

const { DirectoryTree } = Tree

const ACCEPT_IMAGE_EXTENSIONS =
  _.join(["image/gif", "image/jpeg", "image/png", "image/bmp"], ",")

class BlobDescriptor {
  public id: string = ""
  public url: string = ""
  public name?: string
  public path: string = ""
  public file?: File
  public uploaded: boolean = false
  public uploading: boolean = false
  public checked: boolean = false
  public blob?: Blob

  public startUpload() {
    this.uploading = true
    this.uploaded = false
  }

  public uploadCompleted() {
    this.uploading = false
    this.uploaded = true
  }
}

function fetchDirectories(storeId: string, basePath: string): Promise<Blob[]> {
  return StorageService.getBlobs(
    StorageService.createBlobQuery()
      .toBuilder()
      .limit(40)
      .bucketId("104101" + storeId)
      .path(basePath)
      .types(BlobType.Directory)
      .build())
    .then(({ elements }) => elements)
}

interface AddDirectoryButtonProps {
  onFinish?: (name: string) => void
}

function AddDirectoryButton(props: AddDirectoryButtonProps) {

  const [visible, setVisible] = useState(false)

  function onFinish(values: any) {
    if (_.isFunction(props.onFinish)) {
      props.onFinish(values["name"])
    }
    setVisible(false)
  }

  const content = (
    <Form layout="vertical" size="small" onFinish={onFinish}>
      <Form.Item
        label="文件夹名称"
        name="name"
        rules={[{ required: true, message: "请输入文件夹名称!" }]}>
        <Input/>
      </Form.Item>
      <Form.Item noStyle>
        <Button htmlType="button" onClick={() => setVisible(false)}>取消</Button>
        <Button type="primary" htmlType="submit">创建</Button>
      </Form.Item>
    </Form>
  )

  return (
    <Popover overlayClassName={classes.addDirectoryPopoverOverlay}
             placement="bottom"
             trigger="click"
             content={content}
             visible={visible}
             onVisibleChange={setVisible}
             arrowPointAtCenter>
      <Button size="small" onClick={() => setVisible(!visible)}>新建文件夹</Button>
    </Popover>
  )
}

interface UploadImageBlobProps {
  url: string
  name?: string
  expandable?: boolean
  uploading?: boolean
  uploaded?: boolean
  onDelete?: () => void
}

function UploadImageBlob(props: UploadImageBlobProps) {
  const { url, name, expandable, uploading, uploaded } = props

  return (
    <div className={classes.uploadImageBlob}>
      <div className={classNames({
        [classes.uploadImageBlobUploading]: uploading,
        [classes.uploadImageBlobUploaded]: uploaded,
        [classes.uploadImageBlobActions]: !uploading && !uploaded,
      })}>
        <DeleteFilled onClick={() => {
          if (_.isFunction(props.onDelete)) {
            props.onDelete()
          }
        }}/>
        {expandable && <ExpandAltOutlined rotate={90}/>}
        {uploading && <LoadingOutlined spin={uploading}/>}
        {uploaded && <CheckCircleOutlined/>}
      </div>
      <img src={url} alt={name}/>
    </div>
  )
}

interface UploadImageBlobButtonProps {
  onChange?: (files: File[]) => void
}

function UploadImageBlobButton(props: UploadImageBlobButtonProps) {
  const inputRef = useRef(null as unknown as HTMLInputElement)
  return (
    <div className={classes.uploadImageBlobButton}
         onClick={() => inputRef.current.click()}>
      <input type="file" ref={inputRef}
             multiple
             accept={ACCEPT_IMAGE_EXTENSIONS}
             onChange={e => {
               if (_.isFunction(props.onChange) && e.target.files) {
                 props.onChange([...e.target.files])
               }
             }}/>
      <PlusOutlined/>
    </div>
  )
}

interface UploadImageBlobsProps {
  storeId?: string
  directory?: string
  multiple?: boolean
  onUploaded?: (blobs: Blob[]) => void
}

UploadImageBlobs.defaultProps = {
  multiple: true,
}

function UploadImageBlobs(props: UploadImageBlobsProps) {
  const { storeId, onUploaded, multiple } = props
  const [blobs, setBlobs] = useState([] as BlobDescriptor[])
  const [directory, setDirectory] = useState("")
  const [uploading, setUploading] = useState(false)
  const [savedBlobs, setSavedBlobs] = useState([] as Blob[])
  const maxSize = 20

  useEffect(() => {
    setDirectory(_.isUndefined(props.directory) ? "" : props.directory)
  }, [props.directory])

  useEffect(() => {
    if (_.isFunction(onUploaded) && !uploading && !_.isEmpty(savedBlobs)) {
      onUploaded(savedBlobs)
    }
  }, [onUploaded, savedBlobs, uploading])

  function onAcceptedFiles(files: File[]) {
    Promise.all(_.map(_.slice(files, 0, maxSize - _.size(blobs)),
      file => new Promise<BlobDescriptor>(resolve => {
        const reader = new FileReader()
        reader.onloadend = function(e) {
          if (e.target) {
            resolve(_.assign(new BlobDescriptor(), {
              url: e.target.result,
              alt: file.name,
              path: file.name,
              file,

            }))
          }
        }
        reader.readAsDataURL(file)
      })))
      .then(newBlobs => [...blobs, ...newBlobs])
      .then(newBlobs => _.uniqWith(newBlobs, (b1, b2) => b1.url === b2.url))
      .then(setBlobs)
  }

  function uploadBlobs(resources: BlobResource[]) {
    return new Promise<Blob[]>((resolve, reject) => {
      if (!_.isEmpty(resources)) {
        const headBlob = resources[0]
        const blob = _.find(blobs, blob => blob.path === headBlob.path) as BlobDescriptor
        blob.startUpload()
        setBlobs([...blobs])
        StorageService
          .storeBlob({
            ...headBlob,
            path: `${directory}/${headBlob.path}`,
          })
          .then(savedBlob => {
            blob.uploadCompleted()
            setBlobs([...blobs])
            uploadBlobs(_.slice(resources, 1))
              .then(savedBlobs => resolve([savedBlob, ...savedBlobs]))
          })
          .catch(reject)
      } else {
        resolve([])
      }
    })
  }

  function onUploadBlobs() {
    const resources = _.map(blobs, blob => ({
      ...blob, bucketId: "104101" + storeId,
    }))
    if (!_.isEmpty(resources)) {
      setUploading(true)
      uploadBlobs(resources)
        .then(setSavedBlobs)
        .finally(() => setUploading(false))
    }
  }

  return (
    <div className={classes.uploadImageBlobs}>
      <header>
        <Button type="primary" size="small"
                disabled={_.isEmpty(blobs)}
                loading={uploading}
                onClick={onUploadBlobs}>提交</Button>
      </header>
      {
        !_.isEmpty(blobs) && <div className={classes.uploadImageBlobList}>
          {
            _.map(blobs, blob =>
              <UploadImageBlob key={blob.path}
                               url={blob.url}
                               name={blob.name}
                               uploaded={blob.uploaded}
                               uploading={blob.uploading}
                               onDelete={() => setBlobs(_.filter(blobs, aBlob => aBlob !== blob))}/>)
          }
          {_.size(blobs) < maxSize && multiple && <UploadImageBlobButton onChange={onAcceptedFiles}/>}
        </div>
      }
      {
        _.isEmpty(blobs) &&
        <Dropzone multiple={multiple}
                  accept={ACCEPT_IMAGE_EXTENSIONS}
                  onDropAccepted={acceptedFiles => onAcceptedFiles(acceptedFiles)}>
          {({ getRootProps, getInputProps }) => (
            <div {...getRootProps()} className={classes.uploadImageBlobDropzone}>
              <input {...getInputProps()} />
              {<DragUploadImageSvg/>}
              <p>将图片或文件夹拖放到此处上传，或点击上传。</p>
              <Typography.Paragraph>为了保证图片的正常使用，仅支持3M以内jpg、jpeg、gif、png格式图片上传。</Typography.Paragraph>
              <Typography.Paragraph strong>支持选择多张图片上传，支持拖拽文件夹上传。</Typography.Paragraph>
            </div>
          )}
        </Dropzone>
      }
    </div>
  )
}

interface ManageImageBlobProps {
  checked?: boolean
  id: string;
  name?: string;
  url: string;
  onCheck?: (checked: boolean) => void
  onDelete?: (id: string) => void
}

function ManageImageBlob(props: ManageImageBlobProps) {
  const { id, url, name, checked, onCheck, onDelete } = props
  return (
    <div className={classes.manageImageBlob}>
      <img src={url} alt={name}/>
      <div className={classes.manageImageBlobTitle}>
        <Checkbox checked={checked}
                  onChange={e => {
                    if (_.isFunction(onCheck)) {
                      onCheck(e.target.checked)
                    }
                  }}
                  children={name}/>
      </div>
      <div className={classes.manageImageBlobActions}>
        <Button type="link" size="small">重命名</Button>
        <Divider type="vertical"/>
        <Button type="link" size="small" onClick={() => {
          message.success("复制成功")
            .then(() => copy(url))
        }}>复制链接</Button>
        <Divider type="vertical"/>
        <Button type="link" size="small"
                onClick={() => {
                  if (_.isFunction(onDelete)) {
                    onDelete(id)
                  }
                }}>删除</Button>
      </div>
    </div>
  )
}

interface ManageImageBlobsProps {
  storeId: string
  directory: string
  onRefresh?: () => void
}

class UseBlobsProps {
  public storeId: string = ""
  public directory: string = ""
  public page: number = 1
}

function usePageBlobs({
                        storeId,
                        directory,
                        page,
                      }: UseBlobsProps) {
  const [pageBlobs, setPageBlobs] = useState(PageList.empty<BlobDescriptor>())
  const [loading, setLoading] = useState(false)

  const refreshPageBlobs = useCallback(() => {
    setLoading(true)
    StorageService.getBlobs(
      StorageService.createBlobQuery()
        .toBuilder()
        .page(page)
        .bucketId("104101" + storeId)
        .path(directory)
        .types(BlobType.File)
        .build())
      .then(pageBlobs => _.assign(PageList.empty<BlobDescriptor>(), {
        ...pageBlobs,
        elements: _.map(pageBlobs.elements, element => _.assign(new BlobDescriptor(), element, {
          blob: element,
        })),
      }))
      .then(setPageBlobs)
      .finally(() => setLoading(false))
  }, [directory, page, storeId])

  useEffect(refreshPageBlobs, [directory, page, refreshPageBlobs, storeId])

  return {
    pageBlobs, setPageBlobs, loading, setLoading,
    refreshPageBlobs,
  }
}

function ManageImageBlobs(props: ManageImageBlobsProps) {
  const { storeId, directory } = props
  const [currentPage, setCurrentPage] = useState(1)
  const [allChecked, setAllChecked] = useState(false)
  const [allIndeterminate, setAllIndeterminate] = useState(false)

  const {
    pageBlobs, setPageBlobs,
    loading, setLoading,
    refreshPageBlobs,
  } = usePageBlobs({ storeId, directory, page: currentPage })

  useEffect(() => {
    if (loading) {
      setAllChecked(false)
      setAllIndeterminate(false)
    }
  }, [loading])

  function onAllBlobCheck(checked: boolean) {
    _.forEach(pageBlobs.elements, blob => {
      blob.checked = checked
    })
    setPageBlobs(_.assign(PageList.empty<BlobDescriptor>(), pageBlobs))
    setAllChecked(checked)
    setAllIndeterminate(false)
  }

  function onBlobCheck(blob: BlobDescriptor, checked: boolean) {
    blob.checked = checked
    const aAllChecked = _.chain(pageBlobs.elements)
      .map(aBlob => aBlob.checked)
      .reduce((sumChecked, aChecked) => sumChecked && aChecked, true)
      .value()

    const aAllUnchecked = _.chain(pageBlobs.elements)
      .map(aBlob => !aBlob.checked)
      .reduce((sumChecked, aChecked) => sumChecked && aChecked, true)
      .value()

    if ((!allChecked && aAllChecked) || (allChecked && !aAllChecked)) {
      setAllChecked(aAllChecked)
    }

    if ((!allIndeterminate && !aAllChecked && !aAllUnchecked) ||
      (allIndeterminate && (aAllChecked || aAllUnchecked))) {
      setAllIndeterminate(!aAllChecked && !aAllUnchecked)
    }

    setPageBlobs(_.assign(PageList.empty<BlobDescriptor>(), pageBlobs))
  }

  function onDeleteBlob(id: string) {
    setLoading(true)
    StorageService.deleteBlob({
      bucketId: "104101" + storeId,
      id,
    })
      .then(refreshPageBlobs)
      .finally(() => setLoading(false))
  }

  function onDeleteBlobs() {
    const blobIds = _.chain(pageBlobs.elements)
      .filter(blob => blob.checked)
      .map(blob => ({
        bucketId: "104101" + storeId,
        id: blob.id,
      }))
      .value()
    setLoading(true)
    StorageService
      .deleteBlobs(blobIds)
      .then(refreshPageBlobs)
      .finally(() => setLoading(false))
  }

  return (
    <Spin spinning={loading}>
      <header>
        <Checkbox indeterminate={allIndeterminate}
                  checked={allChecked}
                  onChange={e => onAllBlobCheck(e.target.checked)}>全选</Checkbox>
        <Button type="link" size="small"
                disabled={!(allIndeterminate || allChecked)}
                onClick={onDeleteBlobs}>删除</Button>
      </header>
      {_.isEmpty(pageBlobs.elements) ? <Empty/> :
        <>
          <div className={classes.manageImageBlobs}>
            {
              _.map(pageBlobs.elements, blob =>
                <ManageImageBlob key={blob.path}
                                 id={blob.id}
                                 url={blob.url}
                                 name={blob.name}
                                 checked={blob.checked}
                                 onCheck={checked => onBlobCheck(blob, checked)}
                                 onDelete={onDeleteBlob}/>)
            }
          </div>
          <Pagination current={currentPage}
                      pageSize={pageBlobs.size}
                      total={pageBlobs.totalSize}
                      onChange={setCurrentPage}/>
        </>}
    </Spin>
  )
}

interface SelectImageBlobProps extends React.HTMLAttributes<HTMLDivElement> {
  url?: string
  name?: string
  multiple?: boolean
  selected?: boolean
  selectedIndex?: number
}

function SelectImageBlob(props: SelectImageBlobProps) {
  const { url = "", name = "", multiple = true, selected, selectedIndex, ...restProps } = props
  const [wh, setWh] = useState([0, 0] as [number, number])
  useEffect(() => {
    const image = new Image()
    image.src = url

    function onImageLoad() {
      setWh([image.width, image.height])
    }

    image.addEventListener("load", onImageLoad)
    return () => image.removeEventListener("load", onImageLoad)
  }, [url])

  return (
    <div className={classes.selectImageBlob} {...restProps}>
      <img src={url} alt={name}/>
      <footer>{`${wh[0]}x${wh[1]}`}</footer>
      {selected && <div className={classes.imageBlobSelected}>
        <span className={classes.imageBlobSelectedIndex}>
          {multiple ? selectedIndex : <CheckOutlined/>}
        </span>
      </div>}
    </div>
  )
}

interface SelectImageBlobsProps {
  storeId: string
  directory: string
  multiple?: boolean
  selectBlobs?: Blob[]
  onSelect: (blobs: Blob[]) => void
}

function SelectImageBlobs(props: SelectImageBlobsProps) {
  const { storeId, directory, multiple, selectBlobs = [], onSelect } = props
  const selectBlobIds = _.map(selectBlobs, blob => blob.id)
  const [currentPage, setCurrentPage] = useState(1)
  const { pageBlobs, loading } = usePageBlobs({ storeId, directory, page: currentPage })
  return <div className={classes.selectImageBlobs}>
    <Spin spinning={loading}>
      {_.isEmpty(pageBlobs.elements) ? <Empty/> :
        <>
          <div className={classes.selectImageBlobsContent}>
            {
              _.map(pageBlobs.elements, (blob: Blob) => {
                const selectedIndexOf = _.indexOf(selectBlobIds, blob.id)
                const included = selectedIndexOf !== -1
                return <SelectImageBlob key={blob.path}
                                        url={blob.url}
                                        name={blob.name}
                                        multiple={multiple}
                                        selected={included}
                                        selectedIndex={selectedIndexOf + 1}
                                        onClick={() => {
                                          if (multiple) {
                                            if (included) {
                                              _.remove(selectBlobs, selectBlob => selectBlob.id === blob.id)
                                              const newSelectBlobs = [...selectBlobs]
                                              onSelect(newSelectBlobs)
                                            } else {
                                              const newSelectBlobs = [...selectBlobs, blob]
                                              onSelect(newSelectBlobs)
                                            }
                                          } else {
                                            onSelect([blob])
                                          }
                                        }}/>
              })
            }
          </div>
          <Pagination current={currentPage}
                      pageSize={pageBlobs.size}
                      total={pageBlobs.totalSize}
                      onChange={setCurrentPage}/>
        </>}
    </Spin>
  </div>
}

interface ImageBlobsProps {
  upload?: UploadImageBlobsProps
  selectable?: boolean
  multiple?: boolean
  selectBlobs?: Blob[]
  onSelect?: (blobs: Blob[]) => void
}

export default function ImageBlobs(props: ImageBlobsProps) {
  const { upload, multiple, selectable, selectBlobs, onSelect } = props
  const { storeId = "" } = useParams<{ storeId: string }>()
  const [directories, setDirectories] = useState([] as Blob[])
  const [directoriesLoading, setDirectoriesLoading] = useState(false)
  const [currentDirectory, setCurrentDirectory] = useState("/")
  const [uploadMode, setUploadMode] = useState(false)

  // Refresh directories.
  const refreshDirectories = useCallback(() => {
    setDirectoriesLoading(true)
    fetchDirectories(storeId, "")
      .then(setDirectories)
      .finally(() => setDirectoriesLoading(false))
  }, [storeId])

  useEffect(() => refreshDirectories(), [refreshDirectories, storeId])

  function onAddDirectory(newDirectory: string) {
    setDirectoriesLoading(prevState => prevState && true)
    StorageService.storeBlob({
      bucketId: "104101" + storeId,
      name: newDirectory,
      path: `/${newDirectory}`,
    }).finally(refreshDirectories)
  }

  function directoryTreeNodes(blobs: Blob[]): { key: string, title?: React.ReactNode }[] {

    interface NodeTitleProps {
      bucketId?: string,
      id?: string
      path?: string
      name?: string
      onRenamed: () => void
      onDeleted: () => void
    }

    function NodeTitle(props: NodeTitleProps) {
      const { id = "", bucketId = "", name = "", path = "", onRenamed, onDeleted } = props
      const [value, setValue] = useState<string>(name)
      const [editable, setEditable] = useState(false)

      function handleRename() {
        setDirectoriesLoading(true)
        StorageService
          .updateBlob(_.assign(new Blob(), {
            id,
            bucketId,
            name: value,
          }))
          .then(() => setEditable(!editable))
          .then(onRenamed)
      }

      function handleDelete() {
        setDirectoriesLoading(true)
        StorageService.deleteBlob({ bucketId, id })
          .then(() => setEditable(!editable))
          .then(onDeleted)
      }

      return (
        <div className={classNames({
          [classes.directoryTreeNode]: !editable,
          [classes.directoryTreeNodeEditable]: editable,
        })}>
          {!editable && <>
            {name}
            <span>
              {path === currentDirectory && <EditOutlined
                onClick={() => setEditable(!editable)}/>}
              {path === currentDirectory && <>
                <Popconfirm title="您确定要删除吗？" placement="bottom" onConfirm={handleDelete}>
                  <DeleteRowOutlined/>
                </Popconfirm>
              </>}
            </span>
          </>}
          {editable && <>
            <Input autoFocus size="small" value={value}
                   onChange={e => setValue(e.target.value)}
                   onPressEnter={() => setEditable(!editable)}/>
            <CheckOutlined onClick={handleRename}/>
            <RollbackOutlined onClick={() => setEditable(!editable)}/>
          </>}
        </div>
      )
    }

    const directories = _.map(blobs, directory => {
      const { path = "" } = directory
      return {
        key: path,
        title: <NodeTitle bucketId={directory.bucketId} id={directory.id}
                          path={directory.path} name={directory.name}
                          onRenamed={refreshDirectories}
                          onDeleted={refreshDirectories}/>,
      }
    })
    return [{
      key: "/",
      title: <div className={classes.directoryTreeNode}>默认文件夹</div>,
    },
      ...directories]
  }

  function onUploaded(blobs: Blob[]) {
    setUploadMode(false)
    if (_.isFunction(upload?.onUploaded)) {
      upload?.onUploaded(blobs)
    }

    if (_.isFunction(onSelect)) {
      onSelect(blobs)
    }
  }

  return (
    <div className={classes.imageBlobs}>
      <div className={classes.directories}>
        <Spin spinning={directoriesLoading}>
          <Button onClick={() => setUploadMode(!uploadMode)}
                  type="primary"
                  size="small">{uploadMode ? "取消上传" : "上传图片"}</Button>
          <AddDirectoryButton onFinish={onAddDirectory}/>
          <DirectoryTree
            blockNode
            className={classes.directoryTree}
            selectedKeys={[currentDirectory]}
            showIcon={false}
            onSelect={(selectedKeys: any[], { selectedNodes }) =>
              setCurrentDirectory(selectedNodes[0].key as string)}
            treeData={directoryTreeNodes(directories)}/>
        </Spin>
      </div>
      <div className={classes.imageBlobsContent}>
        {!uploadMode && !selectable &&
        <ManageImageBlobs storeId={storeId}
                          directory={currentDirectory}/>}
        {!uploadMode && selectable &&
        <SelectImageBlobs storeId={storeId}
                          multiple={multiple}
                          selectBlobs={selectBlobs}
                          onSelect={blobs => {
                            if (_.isFunction(onSelect)) {
                              onSelect(blobs)
                            }
                          }}
                          directory={currentDirectory}/>}
        {uploadMode && <UploadImageBlobs storeId={storeId}
                                         multiple={props.upload?.multiple}
                                         directory={currentDirectory}
                                         onUploaded={onUploaded}/>}
      </div>
    </div>
  )
}

interface ImageBlobsModalProps extends ImageBlobsProps {
  visible?: boolean
  onCancel?: () => void
}

export function ImageBlobsModal(props: ImageBlobsModalProps) {
  const { selectable = true, multiple, visible, upload, onSelect, onCancel } = props
  const [selectBlobs, setSelectBlobs] = useState([] as Blob[])

  function onOk() {
    if (_.isFunction(onSelect) && !_.isEmpty(selectBlobs)) {
      onSelect(selectBlobs)
      setSelectBlobs([])
    }
  }

  return <Modal visible={visible} width="800px" onCancel={onCancel} onOk={onOk}>
    <ImageBlobs{...{ selectable, multiple, upload }}
               selectBlobs={selectBlobs} onSelect={setSelectBlobs}/>
  </Modal>
}
